package com.smzd.framework.sso;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentMap;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.web.savedrequest.SavedRequest;
import org.springframework.stereotype.Controller;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import com.smzd.framework.sso.domain.SocialUser;
import com.smzd.framework.system.business.UserBindService;
import com.smzd.framework.system.business.UserService;
import com.smzd.framework.system.domain.Meeting;
import com.smzd.framework.system.domain.User;
import com.smzd.framework.system.domain.UserBind;
import com.smzd.framework.utils.ConcurrentMapUtil;
import com.smzd.framework.utils.JsonHelper;
import com.smzd.framework.utils.MeetingSessionHolder;
import com.smzd.framework.utils.URIStacks;
import com.smzd.utils.SessionHolder;
import com.smzd.utils.UUIDUtil;

@Controller
public class SocialAccountController implements InitializingBean {
    public static final int STRATEGY_SILENCE_REGISTER = 0;
    public static final int STRATEGY_CUSTOMIZE_USER = 1;
    public static final int STRATEGY_LOCAL_LOGIN = 2;
    protected String ticketParamName = "code";
    @Autowired(required = false)
    protected OAuth2ProcessorContainer container;
    protected List<? extends OAuth2AuthenticationProcessor> processors;
    protected int strategy = STRATEGY_CUSTOMIZE_USER;
    private Integer meetingId = 1;

    @Autowired
    private UserBindService userBindService;
    @Autowired
    private UserService userService;

    @RequestMapping("/sso/xtwl_getticket")
    public String processAccount(Map<String, Object> model, HttpServletRequest request) throws Exception {
        URIStacks stacks = SessionHolder.getObject(URIStacks.URISTACKS_SESSION_KEY);
        if (stacks == null) {
            throw new Exception("没有URIStacks！");
        }
        if (stacks.size() <= 0) {
            throw new Exception("URIStacks是空的！");
        }
        SavedRequest dsr = stacks.pop();
        // TODO
        // 根据返回来的数据获得第三方帐户信息，并将其存放在Session中。类型？User？
        String state = request.getParameter("state");
        if (state == null) {
            state = "xtwlcas";
        }
        String queryString = request.getQueryString();
        System.out.println("queryString=" + queryString);
        OAuth2AuthenticationProcessor chosenProcessor = null;
        for (OAuth2AuthenticationProcessor processor : processors) {
            if (processor.support(state)) {
                chosenProcessor = processor;
                break;
            }
        }
        if (chosenProcessor != null) {
            synchronized (this) {
                if (StringUtils.isEmpty(chosenProcessor.getRoot())) {
                    String root = request.getSession().getServletContext().getRealPath("/files");
                    chosenProcessor.setRoot(root);
                }
            }
            SocialUser socialUser = chosenProcessor.getSocialUser(queryString, request);

            MeetingSessionHolder.setSocialUser(socialUser);
            return "redirect:" + dsr.getRedirectUrl();
        }
        throw new Exception("没有有效的处理器！state=" + state);
    }

    /**
     * 登录的第二阶段，处理远程用户和本地用户的关系。<br>
     * 可能直接本地用户登录，可能需要新注册，并且包括静默注册和手动填写两种，甚至包括本地用户登录。<br>
     * 社交用户已经得到了，要怎样做呢？
     * 
     * @param request
     * @return
     * @throws Exception
     */
    @RequestMapping("/sso/retrieve")
    public String toRetrieveSocialUser(Map<String, Object> model, final HttpServletRequest request) throws Exception {
        URIStacks stacks = checkURIStacks();
        final SocialUser socialUser = MeetingSessionHolder.getSocialUser();
        if (socialUser == null) {
            throw new Exception("没有社交用户！");
        }
        UserBind userBind = userBindService.getUserBindByOpenid(socialUser.getOpenid());
        final User localUser;
        if (userBind == null) {// 并没有绑定本地用户
            if (strategy == STRATEGY_SILENCE_REGISTER) {
                // 自动注册本地用户
                localUser = silenceRegistUser(socialUser);
            } else if (strategy == STRATEGY_CUSTOMIZE_USER) {
                // TODO 如果要完善用户信息呢？
                // SessionBasedRedirectRequest req = new
                // SessionBasedRedirectRequest(request, "/sso/customreg");
                // stacks.push(req);
                // 远程用户已经找到，但本地用户没有找到，现在要完善用户信息，或者本地登录了
                SavedRequest saved = stacks.clear();// 得到受保护资源的访问请求
                Assert.notNull(saved);// 如果为空，我也不知道该怎么办了，这是不应该的现象

                stacks.push(saved);
                stacks.push(new SessionBasedRedirectRequest(request, "/sso/preparedauth"));
                return "redirect:/sso/customize";
            } else if (strategy == STRATEGY_LOCAL_LOGIN) {
                SavedRequest sr = stacks.clear();
                stacks.push(sr);
                return "redirect:/sso/bind";
            }
            throw new Exception("未知的策略！strategy=" + strategy);
        } else {// 如果已经绑定了本地用户，则
            localUser = userService.getItemById(userBind.getOwner().getId());
        }
        // 本地用户处理成功，可以登陆了，也可能已经登录了，这里在绑定用户。不管怎么样，社交用户已经有了本地对应帐户，
        // 可以处理其好友关系了。这里启动一个线程，来把社交好友处理一下。
        final Object accessToken = SessionHolder.getObject("OBJECT_ACCESS_TOKEN");
        new Thread() {
            @Override
            public void run() {
                System.out.println("开始处理好友关系！");
                OAuth2AuthenticationProcessor chosenProcessor = null;
                for (OAuth2AuthenticationProcessor processor : processors) {
                    if (processor.getPlatform() == socialUser.getPlatform()) {
                        chosenProcessor = processor;
                        break;
                    }
                }
                System.out.println("处理器为：" + chosenProcessor);
                if (chosenProcessor != null) {
                    try {
                        List<String> openids = chosenProcessor.getFriendOpenids(socialUser, accessToken);
                        System.out.println("当前人员的好友的openId列表：" + openids);
                        FriendDetecter detecter = chosenProcessor.getFriendDetecter();
                        System.out.println("好友处理器是：" + detecter);
                        for (String openid : openids) {
                            System.out.println("处理：openid='" + openid + "'的好友");
                            UserBind bind = userBindService.getUserBindByOpenid(openid);
                            if (bind != null) {// 这个社交在本地有帐户了
                                System.out.println("这个好友有本地帐户！处理之");
                                User friendUser = bind.getOwner();
                                // 下面呢？不知道该怎么办了
                                detecter.processFriendship(chosenProcessor.getPlatform(), localUser, friendUser);
                            }// 社交帐户在本地没有帐户很正常，不管就是了
                            else {
                                System.out.println("这个好友没有本地帐户，忽略之！");
                            }
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }

        }.start();

        // 跳转到认证？
        MeetingSessionHolder.setPreparedUser(localUser);
        SavedRequest dsr = stacks.pop();
        return "redirect:" + dsr.getRedirectUrl();
    }

    /**
     * 社交登录回来之后的简易注册，完善用户个人信息页面。不允许随便调用。
     * 
     * @param model
     * @return
     * @throws Exception
     */
    @RequestMapping("/sso/customize")
    public String toCustomizePage(Map<String, Object> model) throws Exception {
        if (MeetingSessionHolder.getLoginUser() != null) {
            throw new Exception("用户已经登录！");
        }
        checkURIStacks();
        SocialUser socialUser = MeetingSessionHolder.getSocialUser();
        if (socialUser == null) {
            throw new Exception("没有看到社交用户！");
        }
        model.put("_social_user_", socialUser);
        return "social/customize.ftl";
    }

    /**
     * 完善用户信息，注册用户。<br>
     * 默认，/sso/customize页面会提交数据到这个地方来。<br>
     * 这个方法不是真实的，因为实际的系统需要自己去实现具体的本地用户完善，因此这里只给出一个例子。<br>
     * 这个例子的关键点在于，当完成的时候，需要根据URIStacks的引导继续后面的流程。<br>
     * 由于在进入/sso/customize页面之前就已经完成了引导数据的注入，因此具体实现的时候只需要从中取数据即可。
     * 
     * @param params
     * @return
     * @throws Exception
     */
    // @RequestMapping("/sso/customreg")
    @Deprecated
    public String silenceRegistCustomizedUser(String params) throws Exception {
        if (MeetingSessionHolder.getLoginUser() != null) {
            throw new Exception("用户已经登录！");
        }
        URIStacks stacks = checkURIStacks();
        SocialUser socialUser = MeetingSessionHolder.getSocialUser();
        if (socialUser == null) {
            throw new Exception("没有看到社交用户！");
        }
        // TODO 将参数设置进入socialUser
        // 注册用户
        User localUser = silenceRegistUser(socialUser);
        MeetingSessionHolder.setPreparedUser(localUser);
        SavedRequest dsr = stacks.pop();
        return "redirect:" + dsr.getRedirectUrl();
    }

    /**
     * 此方法必须登录之后才能访问
     * 
     * @return
     * @throws Exception
     */
    @RequestMapping("/sso/bind")
    @ResponseBody
    public String bindSocialUserAndLocalUser(String code) throws Exception {
        User localUser = MeetingSessionHolder.getLoginUser();
        if (localUser == null) {
            throw new Exception("用户尚未登录！");
        }
        SocialUser socialUser = (SocialUser) ConcurrentMapUtil.getUserConcurrentMap().get(code);
        if (socialUser == null) {
            throw new Exception("没有找到社交用户！");
        }
        // 先查找一下该用户是不是已经绑定在了别人身上
        UserBind userBind = userBindService.getUserBindByOpenid(socialUser.getOpenid());
        if (userBind != null) {// 已经绑定过了
            throw new Exception("该社交用户已经绑定过了！");
        }

        // 再查一下该用户是不是已经绑定在了自己身上//这个可以不用查了，因为只要被绑定过就不允许再绑定了

        userBind = new UserBind();
        userBind.setOwner(localUser);
        userBind.setName(socialUser.getName());
        userBind.setOpenid(socialUser.getOpenid());
        userBind.setType(socialUser.getPlatform());
        userBindService.insert(userBind);
        return JsonHelper.getSuccessJsonString();
    }

    /**
     * 此方法必须登录之后才能访问
     * 
     * @return
     * @throws Exception
     */
    @RequestMapping("/sso/unbind")
    @ResponseBody
    public String unbindSocialUser(String code) throws Exception {
        User localUser = MeetingSessionHolder.getLoginUser();
        if (localUser == null) {
            throw new Exception("用户尚未登录！");
        }
        SocialUser socialUser = (SocialUser) ConcurrentMapUtil.getUserConcurrentMap().get(code);
        if (socialUser == null) {
            throw new Exception("没有找到社交用户！");
        }
        // 先查找一下该用户是不是已经绑定在了别人身上
        UserBind userBind = userBindService.getUserBindByOpenid(socialUser.getOpenid());
        if (userBind == null) {// 该社交用户没有绑定过
            throw new Exception("该社交用户尚未绑定到本地用户！");
        }
        userBindService.remove(userBind);
        return JsonHelper.getSuccessJsonString();
    }

    @RequestMapping("/sso/preparebindforapp")
    public String prepareBindSocialUserForOAuth2() throws Exception {
        SocialUser socialUser = MeetingSessionHolder.getSocialUser();
        ConcurrentMap<String, User> map = ConcurrentMapUtil.getUserConcurrentMap();
        String uuid = UUIDUtil.getRandomUUID();
        map.put(uuid, socialUser);
        URIStacks stacks = checkURIStacks();
        SavedRequest saved = stacks.clear();
        String redirect_uri = saved.getRedirectUrl();
        if (redirect_uri.indexOf("?") > 0) {
            redirect_uri += "&code=" + uuid;
        } else {
            redirect_uri += "?code=" + uuid;
        }
        System.out.println("我要跳转回到应用了！地址：\n" + redirect_uri);
        return "redirect:" + redirect_uri;
    }

    @RequestMapping("/sso/redirectnext")
    public String redirectToNextRequest() throws Exception {
        URIStacks stacks = checkURIStacks();
        SavedRequest saved = stacks.pop();
        String target = saved.getRedirectUrl();
        return "redirect:" + target;
    }

    protected URIStacks checkURIStacks() throws Exception {
        URIStacks stacks = SessionHolder.getObject(URIStacks.URISTACKS_SESSION_KEY);
        if (stacks == null) {
            throw new Exception("没有得到URIStacks！");
        }
        if (stacks.size() <= 0) {
            throw new Exception("URIStacks是空的!");
        }
        return stacks;
    }

    private User silenceRegistUser(SocialUser socialUser) {
        User localUser = socialUser;
        Meeting meeting = getSilenceRegistUserOwner();
        localUser.setOwner(meeting);
        localUser.setType(User.USER_TYPE_MEETING_MANAGER);
        userService.insert(localUser);
        // 自动添加本地用户和远程用户的绑定
        UserBind userBind = new UserBind();
        userBind.setName(socialUser.getName());
        userBind.setOpenid(socialUser.getOpenid());
        userBind.setType(socialUser.getPlatform());
        // 插入前换成本地用户，并返回。
        // 其实这个本地用户不用查数据库也行，直接从SocialUser转换过来就行了。
        localUser = userService.getItemById(localUser.getId());
        userBind.setOwner(localUser);
        userBindService.insert(userBind);
        return localUser;
    }

    protected Meeting getSilenceRegistUserOwner() {
        Meeting meeting = new Meeting();
        meeting.setId(meetingId);
        return meeting;
    }

    public String getTicketParamName() {
        return ticketParamName;
    }

    public void setTicketParamName(String ticketParamName) {
        this.ticketParamName = ticketParamName;
    }

    public List<? extends OAuth2AuthenticationProcessor> getProcessors() {
        return processors;
    }

    public void setProcessors(List<? extends OAuth2AuthenticationProcessor> processors) {
        this.processors = processors;
    }

    public int getStrategy() {
        return strategy;
    }

    public void setStrategy(int strategy) {
        this.strategy = strategy;
    }

    public Integer getMeetingId() {
        return meetingId;
    }

    public void setMeetingId(Integer meetingId) {
        this.meetingId = meetingId;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        if (container != null) {
            processors = container.getProcessors();
        }
        if (processors != null) {
            System.out.println("通过容器注入" + processors.size() + "个处理器！");
        } else {
            System.out.println("没有注入处理器容器！");
            processors = new ArrayList<>();
        }
    }

}
